package proxy

import (
    "net"
    "time"
    "sync"
    "log"
    "runtime"
    "bufio"
    "io"
    "unicode"
)

type Server struct {
    Addr string

    ReadTimeout       time.Duration
    ReadHeaderTimeout time.Duration
    WriteTimeout      time.Duration
    IdleTimeout       time.Duration

    ErrorLog *log.Logger

    mu         sync.Mutex
}

func (s *Server) readHeaderTimeout() time.Duration {
    if s.ReadHeaderTimeout != 0 {
        return s.ReadHeaderTimeout
    }
    return s.ReadTimeout
}


func (s *Server) logf(format string, args ...interface{}) {
    if s.ErrorLog != nil {
        s.ErrorLog.Printf(format, args...)
    } else {
        log.Printf(format, args...)
    }
}

var (
    bufioReaderPool   sync.Pool
    bufioWriter2kPool sync.Pool
    bufioWriter4kPool sync.Pool
)

var copyBufPool = sync.Pool{
    New: func() interface{} {
        b := make([]byte, 32*1024)
        return &b
    },
}

func bufioWriterPool(size int) *sync.Pool {
    switch size {
    case 2 << 10:
        return &bufioWriter2kPool
    case 4 << 10:
        return &bufioWriter4kPool
    }
    return nil
}

func newBufioReader(r io.Reader) *bufio.Reader {
    if v := bufioReaderPool.Get(); v != nil {
        br := v.(*bufio.Reader)
        br.Reset(r)
        return br
    }
    // Note: if this reader size is ever changed, update
    // TestHandlerBodyClose's assumptions.
    return bufio.NewReader(r)
}

func putBufioReader(br *bufio.Reader) {
    br.Reset(nil)
    bufioReaderPool.Put(br)
}

func newBufioWriterSize(w io.Writer, size int) *bufio.Writer {
    pool := bufioWriterPool(size)
    if pool != nil {
        if v := pool.Get(); v != nil {
            bw := v.(*bufio.Writer)
            bw.Reset(w)
            return bw
        }
    }
    return bufio.NewWriterSize(w, size)
}

func putBufioWriter(bw *bufio.Writer) {
    bw.Reset(nil)
    if pool := bufioWriterPool(bw.Available()); pool != nil {
        pool.Put(bw)
    }
}


type conn struct {
    server *Server

    rwc net.Conn

    remoteAddr string

    bufr *bufio.Reader

    bufw *bufio.Writer


    peerHost string
    peerrwc net.Conn
}


func (srv *Server) newConn(rwc net.Conn) *conn {
    c := &conn{
        server: srv,
        rwc:    rwc,
    }
    return c
}

func (c *conn) connect() (err error) {
    c.peerrwc, err = net.Dial("tcp", c.peerHost)
    if err != nil {
        return
    }
    return
}

func (c *conn) relay() {
    // TODO:

    //c.bufr.Read()
    //c.peerrwc.Write()
    //
    //c.peerrwc.Read()
    //c.bufw.Write()
}

func (c *conn) close() {
    c.bufw.Flush()
    c.rwc.Close()


    c.peerrwc.Close()
}

type proxy interface{
    Serve()
}

func (c *conn) Serve() {
    c.remoteAddr = c.rwc.RemoteAddr().String()
    defer func() {
        if err := recover(); err != nil {
            const size= 64 << 10
            buf := make([]byte, size)
            buf = buf[:runtime.Stack(buf, false)]
            c.server.logf("proxy: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
        }
        c.close()
    }()

    if d := c.server.ReadTimeout; d != 0 {
        c.rwc.SetReadDeadline(time.Now().Add(d))
    }

    c.bufr = newBufioReader(c.rwc)
    c.bufw = newBufioWriterSize(c.rwc, 4<<10)

    bytes, err := c.bufr.Peek(1)
    if len(bytes) != 1 {
        if err != nil {
            log.Print(err)
        }
        return
    }

    var p proxy

    r := rune(bytes[0])
    if r == 4 || r == 5 {
        p = c.newSocks(SocksVer(r))
    } else if unicode.IsLetter(r) {
        p = c.newHttp()
    } else {
        log.Printf("%s unsupported protocol", c.remoteAddr)
        return
    }

    p.Serve()
}


func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()

    var tempDelay time.Duration // how long to sleep on accept failure

    for {
        rw, e := l.Accept()
        if e != nil {
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                } else {
                    tempDelay *= 2
                }
                if max := 1 * time.Second; tempDelay > max {
                    tempDelay = max
                }
                srv.logf("proxy: Accept error: %v; retrying in %v", e, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return e
        }
        tempDelay = 0
        c := srv.newConn(rw)
        go c.Serve()
    }
}

func (srv *Server) ListenAndServe() error {
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(ln)
}



func ListenAndServe(addr string) error {
    server := &Server{Addr: addr}
    return server.ListenAndServe()
}
